/******************************************
 * EE563 Example Project 6 - Scene Graph Example with Graphics
 * by: Derek Molloy
 ******************************************/

#include <windows.h>  // Header File For Windows
#include <GL/gl.h>	  // Header File For The OpenGL32 Library
#include <GL/glu.h>	  // Header File For The GLu32 Library
#include <string>
#include <sstream>
#include "Vec3.h"
#include "Matrix.h"
#include "Vec4.h"
#include "Quat.h"
#include "SceneGraph.h"
#include "DummyObject.h"
#include "Primitive.h"
#include "Transform.h"
#include "Light.h"
#include "Texture.h"
#include <iostream>
#include "Model.h"
#include "Camera.h"
#include "Projectile.h"

using namespace std;


HDC			hDC=NULL;  // Private GDI Device Context
HGLRC		hRC=NULL;  // Permanent Rendering Context
HWND		hWnd=NULL; // Holds Our Window Handle
HINSTANCE	hInstance; // Holds The Instance Of The Application
GLfloat     fov = 60.0f;
int         downX = 0, downY = 0;
int         upX = 0, upY = 0;
float       xOffset = 0.0f, yOffset = 0.0f, zOffset = -5.0f, xPos=-3.0f, yPos=2.5f;
SceneGraph  *sg;
Camera      *camera;
Light       *l;
Primitive   *p1;
Projectile *missile;

// Function Declarations

LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, 
        LPARAM lParam);
void enableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC);
int initGL(GLvoid);
int drawGLScene(float theta);
void disableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC);
void defineGLSphere(GLfloat, GLfloat);
void increaseFOV();
void decreaseFOV();
void setFOV(GLfloat);
void GetFramesPerSecond();

// WinMain - the starting point of our application

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int iCmdShow)
{
    WNDCLASS wc;
    HGLRC hRC;        
    hInstance = hInst;
    MSG msg;
    BOOL bQuit = FALSE;
    float theta = 0.0f;

    // register window class 
    wc.style = CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "EE563GLExample";
    RegisterClass (&wc);

    // create main window 
    hWnd = CreateWindow ( "EE563GLExample", "EE563 Example", 
      WS_CAPTION | WS_POPUPWINDOW | WS_VISIBLE | WS_MAXIMIZEBOX |WS_SIZEBOX, 
      0, 0, 256, 256,
      NULL, NULL, hInstance, NULL);
      
    // enable OpenGL for the window 
    enableOpenGL (hWnd, &hDC, &hRC);
    initGL();
    
    // program main loop 
    while (!bQuit)
    {
        // check for messages 
        if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
        {
            // handle or dispatch messages 
            if (msg.message == WM_QUIT)
            {
                bQuit = TRUE;
            }
            else
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }
        else
        {
            drawGLScene(theta);
            SwapBuffers (hDC);
            theta += 10.0f;
            Sleep (1000);
        }
    }
    // shutdown OpenGL
    disableOpenGL (hWnd, hDC, hRC);
    // destroy the window explicitly 
    DestroyWindow (hWnd);
    return msg.wParam;
}


// Window Callback Process

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int cx, cy;
    double aspect;
        
    switch (message)
    {
        case WM_RBUTTONDOWN:  // if the right button is down
             setFOV(60);
             xOffset=0.0f;
             yOffset=0.0f;
             xPos=-3.0f;
             yPos=2.5f;             
             return 0;  
        case WM_LBUTTONDOWN:
             downX = (int)(short)LOWORD(lParam); 
             downY = (int)(short)HIWORD(lParam); 
             std::cout << "downx " <<downX<<std::endl; 
             std::cout << "downy" << downY<<std::endl; 
       case WM_LBUTTONUP:
             upX = (int)(short)LOWORD(lParam); 
             upY = (int)(short)HIWORD(lParam); 
             if (downX != upX){
                std::cout << "upx " <<upX<<std::endl; 
                std::cout << "upy" << upY<<std::endl; 

                int diffx = upX-downX;
                int diffy = downY-upY;
                
                std::cout << "diffx " << diffx<<std::endl; 
                std::cout << "diffy" << diffy<<std::endl; 
                        
                xOffset+= ((float)diffx)/30;
                yOffset+= ((float)diffy)/30;
             }
             
        case WM_MOUSEMOVE:    // if the mouse is moved
             if (wParam == MK_LBUTTON) // and the left mouse button is down
             {
                
             }
             return 0;
        case WM_CREATE:
            return 0;
        case WM_CLOSE:
            PostQuitMessage (0);
            return 0;
        case WM_SIZE:
             RECT rect;
	         GetClientRect(hWnd, &rect);
	         cx = rect.right - rect.left;
             cy = rect.bottom - rect.top;
          	 aspect = (cy == 0) ? cx : (double)cx/(double)cy;
	         glViewport(0, 0, cx, cy);
             return 0;
        case WM_DESTROY:
            return 0;
        case WM_KEYDOWN:
            switch (wParam)
            {
            case VK_ESCAPE:
                 PostQuitMessage(0);
                 return 0;
            case VK_LEFT:
                 //xOffset-=0.1f;
                 xPos-=0.1f;
                 return 0;
            case VK_RIGHT:
                 //xOffset+=0.1f;
                 xPos+=0.1f;
                 return 0;
            case VK_UP:
                 //yOffset+=0.1f;
                 yPos+=0.1f;
                 return 0;
            case VK_DOWN:
                 //yOffset-=0.1f;
                 yPos-=0.1f;
                 return 0;                 
            case VK_F1: //F1 key pressed
                 std::cout << "move projectile" << std::endl; 
                 missile->setTrans(Vec3(xPos, yPos, 5.0f));
                 return 0;
            }
            return 0;
        case WM_CHAR:
             switch(wParam)
             {
               case 'a': 
                  decreaseFOV();
                  return 0;        
               case 's':
                  increaseFOV();
                  return 0;      
               case 'l':  
                 std::cout << "toggled" << std::endl; 
                 l->toggleOn();                
                 return 0;               
             }
             return 0;
        default:
            return DefWindowProc (hWnd, message, wParam, lParam);
    }
}


//Enable OpenGL

void enableOpenGL (HWND hWnd, HDC *hDC, HGLRC *hRC)
{
    PIXELFORMATDESCRIPTOR pfd;
    int iFormat;
    // get the device context (DC) 
    *hDC = GetDC (hWnd);

    // set the pixel format for the DC 
    ZeroMemory (&pfd, sizeof (pfd));
    pfd.nSize = sizeof (pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;
    iFormat = ChoosePixelFormat (*hDC, &pfd);
    SetPixelFormat (*hDC, iFormat, &pfd);

    // create and enable the render context (RC) 
    *hRC = wglCreateContext( *hDC );
    wglMakeCurrent( *hDC, *hRC );
}

// Setup our GL Scene
int initGL(GLvoid)										
{
	glShadeModel(GL_SMOOTH);            // Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// white Background
	glClearDepth(1.0f);					// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);			// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);				// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	
                                   // Really Nice Perspective Calculations
    glEnable(GL_TEXTURE_2D);	
    
    //Useful debug code... pops up a message on the screen.
    
    //std::stringstream str;
    //str << "Width=" << t->getWidth() << " Height=" << t->getHeight(); 
    //::MessageBox(0, str.str().c_str(), "Texture Test", MB_OK);
    
    sg = new SceneGraph;
    DummyObject *root = new DummyObject("Root");
    sg->bindRoot(root);
    
    //add a camera
    camera = new Camera();
    root->addChild(camera);
    
    //light
    l = new Light();
    l->setPosition(Vec4(1.5f, 0.5f, 3.0f, 1.0f));    
    //spot on 1
    //l->setSpot();
    root->addChild(l);
    
    //add projectile
    Matrix pm;
    pm.makeTranslate(-3.0f, 2.5f, 5.0f);
    missile = new Projectile(pm);
    missile->getBullet()->getMaterial()->setDiffuse(Vec4(0.9f, 0.2f, 0.2f, 1.0f));  //almost white
    Texture *texBull = new Texture("ball.bmp");
    missile->getBullet()->setTexture(texBull);
    root->addChild(missile);
    
    Matrix lm;
    lm.makeTranslate(10.0f, 0.5f, 10.0f);
    Transform *lt = new Transform(lm);
    Light *l2 = new Light();
    l2->setPosition(Vec4(0.0f, -10.0f, 10.0f, 1.0f));
    //spot on 2
    //l2->setSpot();
    //root->addChild(l2);
    //root->addChild(lt);
    
    Matrix m0, m1, m2, m3, m4, m5;
    m0.makeScale(0.005f, 0.005f, 0.005f); // lamp is way too big!
    Transform *t0  = new Transform(m0);
    Model *lamp = new Model("lamp1.3ds");
    root->addChild(t0);
    t0->addChild(lamp);
    t0->addChild(l2);

   
    lt->addChild(l2);
    t0->addChild(lt);
    
    m1.makeTranslate(3.0f, 4.0f, 1.0f);
    Transform *t1 = new Transform(m1);
    root->addChild(t1);
    p1= new Primitive(CUBE, 2.5f, 20.0f);  //blue by default
    p1->getMaterial()->setDiffuse(Vec4(0.9f, 0.2f, 0.2f, 1.0f));  //almost white
    Texture *tex1 = new Texture("Texture.bmp");
    p1->setTexture(tex1);
    t1->addChild(p1);
    
    m2.makeTranslate(-3.0f, 0.0f, 1.0f);
    Transform *t2 = new Transform(m2);
    root->addChild(t2);
    Primitive *p2 = new Primitive(CUBE, 2.5f, 10.0f);
    p2->getMaterial()->setDiffuse(Vec4(0.9f, 0.9f, 0.9f, 1.0f));  //almost white
    Texture *tex2 = new Texture("Texture2.bmp");
    p2->setTexture(tex2);
    t2->addChild(p2);
    
    m3.makeScale(0.01f, 0.01f, 0.01f);
    m4.makeTranslate(0.0f, 2.5f, 0.0f);
    m5 = m3 * m4;
    Transform *t3  = new Transform(m5);
    p2->addChild(t3);
    Model *chesspawn = new Model("chesspawn.3ds");
    t3->addChild(chesspawn);
        
	return TRUE;						// Initialization Went OK
}

void increaseFOV() {    
     if (fov<180.0f) fov+=1.0;
}

void decreaseFOV(){
     if (fov>0.0f) fov-=1.0;
}

void setFOV(GLfloat f){
     if (f>0.0f && f<180.0f) fov = f;
}

// Called to update the scene - give us the animation
int drawGLScene(float theta)			// Draw the scene; 
{                                       
    /*Camera *camera = new Camera();
    camera->render(0.0f);
    camera->update(0.0f);*/
    //change camera position
    Vec3 pos = camera->getPos();
    pos[0]=xOffset;
    pos[1]=yOffset;
    camera->setPos(pos);
    //change camera perspective/fov
    Vec4 pers = camera->getPerspective();
    pers[0]=fov;
    camera->setPerspective(pers);
    camera->setRot(Vec4(theta,0.0f,1.0f,0.0f));
    sg->renderScene(0.0f);  
    sg->collideScene(missile->getBullet(),0.0f);     
    //sg->updateScene(0.0f);
    //sg->setSceneCamera(camera,0.0f);
    GetFramesPerSecond();
	return TRUE;   // Keep Going
}

// Get the current Frame Rate.
void GetFramesPerSecond()
{
   static char titleBar[25] = {0};
   
   static int fps = 0;   //number of frames per second in between seconds.
   float nextSecond = 0.0f;
   static float lastSecond = 0.0f;

   // Add to the frames per second every time this function is called.
   fps++;

   // Get the second in millisecond convert to seconds.
   nextSecond = GetTickCount() * 0.001f;				

   // If a second has passed then display the FPS number.
   if(nextSecond - lastSecond > 1.0f)
   {
     lastSecond = nextSecond;
	 sprintf(titleBar, "EE563 Example %d FPS", fps);
     SetWindowText(hWnd, titleBar);
     fps = 0;
   }
}


// Disable OpenGL 
void disableOpenGL (HWND hWnd, HDC hDC, HGLRC hRC)
{
    wglMakeCurrent (NULL, NULL);
    wglDeleteContext (hRC);
    ReleaseDC (hWnd, hDC);
}
